Le mapping objet-relationnel vous permet de définir dans quelles tables et colonnes de votre base de données l'état de votre entité sera sauvegardée.
Les outils objet-relationnel vous permettent de travailler avec des objets plutôt qu'avec des tables et des colonnes et génèrent automatiquement le code SQL pour lire et mettre à jour la base de données. De cette manière, vous n'avez pas besoin d'un accès direct à la base de données SQL. Bien sûr, vous devez définir précisément la manière dont vos classes correspondent aux tables et ce travail est effectué à l'aide des annotations JPA. Les entités OpenXava sont des entités JPA, c'est pourquoi la persistance dans OpenXava est prise en charge par le Java Persistance API (JPA). Ce chapitre montre les techniques de base de mise en correspondance ainsi que quelques cas spéciaux. Si vous souhaitez en savoir plus sur JPA , consultez la documentation de Hibernate Annotations (l'implémentation JPA utilisée par défaut par OpenXava) ou tout autre manuel JPA que vous souhaitez.
Mapping d'entité
L'annotation @Table spécifie la table principale pour l'entité annotée. Des tables additionnelles peuvent être ajoutées avec les annotations @SecondaryTable ou @SecondaryTables. Si aucune annotation @Table n'est présente, une valeur par défaut sera appliquée. Exemple :
L'annotation @Column est utilisée pour spécifier la colonne d'une propriété persistante ou champ. Si aucune annotation @Column est écrite, une valeur par défaut est appliquée. Une simple exemple :
Si vous avez besoin de définir un mapping pour des clés étrangères composites, il faut employer l'annotation @JoinColumns. Cette annotation groupe plusieurs @JoinColumn pour la même référence. Lorsque l'annotation @JoinColumns est utilisée, les deux attributs name et referencedColumnName doivent être définis dans chaque annotation @JoinColumn. Exemple :
Lorsque vous utilisez l'annotation @OneToMany pour une collection, le mapping dépende de la référence utilisée dans l'autre partie de l'association, c'est-à-dire que, généralement, il n'y a rien besoin de faire. Mais si vous utilisez l'annotation @ManyToMany, il est utile de spécifier la table de jointure avec l'annotation @JoinTable comme ceci :
Si l'annotation @CollectionTable au @AttributeOverrides manque, des valeurs par défaut sont appliquées
Mapping de référence embarquée
Une référence embarquée contient des données qui sont stockées dans la même table du modèle relationnel que l'entité principale. Par exemple, si vous avez une entité Address (adresse) embarquée associée à une entité Customer (acheteur), les données de l'adresse seront enregistrées dans la même table que les données de l'acheteur. Comment réaliser la mise en correspondance avec JPA ? En utilisant l'annotation @AttributeOverrides de cette manière :
Si vous n'utilisez par @AttributeOverrides, des valeurs par défaut s'appliquent.
Conversion de type
La conversion de type entre Java et la base de donnée relationnelle est le travail de l'implémentation JPA (OpenXava utilise Hibernate par défaut). Normalement, la conversion par défaut est correcte pour la plupart des cas, mais si vous travaillez avec une base de donnée existante, vous auriez besoin des quelques astuces de cette section.
Etant donné que OpenXava utilise les facilités d'Hibernate pour les conversions de type, vous pouvez en apprendre plus sur ce sujet en lisant la documentation Hibernate.
Conversion de propriétés
Lorsque le type d'une propriété Java et le type de la colonne de la base de donnée relationnelle ne correspondent pas, vous devez écrire un type Hibernate (Hibernate Type) afin d'exécuter votre conversion personnalisée.
Par exemple, si vous avez une propriété de type String[] et que vous souhaitez enregistrer sa valeur en l'agrégeant dans une seule colonne de table de type VARCHAR, vous devez déclarer la conversion de votre propriété ainsi :
Le convertisseur de type doit implémenter l'interface org.hibernate.usertype.UserType(1). Les principales méthodes sont nullSafeGet() (2) pour lire la valeur de la base de donnée et la convertir en Java et nullSafeSet() (3) pour écrire la valeur Java dans la base de données.
OpenXava offre des type génériques de convertisseurs dans le paquet org.openxava.types prêts à l'emploi. L'un de ceux-là est le convertisseur EnumLetterType qui permet de mettre en correspondance des type enum. Par exemple, si vous avez une propriété telle que celle-ci :
private Distance distance;publicenum Distance { LOCAL, NATIONAL, INTERNATIONAL };
Dans cette propriété Java, 'LOCAL' vaut 1, 'NATIONAL' vaut 2 et 'INTERNATIONAL' vaut 3 lorsque la propriété est enregistrée dans la base de données. Mais que se passe-t-il si dans la base de données, une lettre doit être enregistrée ? Dans ce cas, vous pouvez utiliser EnumLetterType de cette façon :
Puisque vous spécifiez 'LNI' comme valeurs de l'attribut letters, le convertisseur de type fait correspondre 'L' à 1, 'N' à 2 et 'I' à 3. Remarquez aussi comment des convertisseurs peuvent être configurés en utilisant leurs propriétés ce qui les rend plus réutilisables.
Conversion de colonnes multiples
Avec l'annotation @CompositeUserType, vous pouvez faire correspondre plusieurs colonnes de table à une seule propriété Java. Ceci peut être utile si vous avez des propriétés définies par des classes personnalisées qui ont elles-mêmes plusieurs propriétés à enregistrer. Cette annotation est aussi utilisée lorsque vous travaillez avec une base de données existante.
Une exemple type est les convertisseur générique Date3Type qui permet de stocker dans 3 colonnes de la base de données une seule propriété Java avec le type java.util.Date.
DAYDELIVERY, MONTHDELIVERY et YEARDELIVERY sont trois colonnes de la base de données qui enregistrent la valeur de la date de livraison. Voici le code source de Date3Type :
packageorg.openxava.types;importjava.io.*;importjava.sql.*;importorg.hibernate.*;importorg.hibernate.engine.*;importorg.hibernate.type.*;importorg.hibernate.usertype.*;importorg.openxava.util.*;/**
* Dans le code java, un <tt>java.util.Date</tt> et dans la base de
* données, 3 colonnes de type entier (integer). <p>
*
* @author Javier Paniza
*/publicclass Date3Type implements CompositeUserType {// 1publicString[] getPropertyNames(){returnnewString[]{"year", "month", "day"};}publicType[] getPropertyTypes(){returnnewType[]{ Hibernate.INTEGER, Hibernate.INTEGER, Hibernate.INTEGER};}publicObject getPropertyValue(Object component, int property)throws HibernateException {// 2
java.util.Date date = (java.util.Date) component;switch(property){case0:
return Dates.getYear(date);case1:
return Dates.getMonth(date);case2:
return Dates.getYear(date);}thrownew HibernateException(XavaResources.getString("date3_type_only_3_properties"));}publicvoid setPropertyValue(Object component, int property, Object value)throws HibernateException // 3{
java.util.Date date = (java.util.Date) component;int intValue = value == null?0:((Number) value).intValue();switch(property){case0:
Dates.setYear(date, intValue);case1:
Dates.setMonth(date, intValue);case2:
Dates.setYear(date, intValue);}thrownew HibernateException(XavaResources.getString("date3_type_only_3_properties"));}publicClass returnedClass(){return java.util.Date.class;}publicboolean equals(Object x, Object y)throws HibernateException {if(x==y)returntrue;if(x==null || y==null)returnfalse;return!Dates.isDifferentDay((java.util.Date) x, (java.util.Date) y);}publicint hashCode(Object x)throws HibernateException {return x.hashCode();}publicObject nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner)throws HibernateException, SQLException// 4{Number year = (Number) Hibernate.INTEGER.nullSafeGet( rs, names[0]);Number month = (Number) Hibernate.INTEGER.nullSafeGet( rs, names[1]);Number day = (Number) Hibernate.INTEGER.nullSafeGet( rs, names[2]);int iyear = year == null?0:year.intValue();int imonth = month == null?0:month.intValue();int iday = day == null?0:day.intValue();return Dates.create(iday, imonth, iyear);}publicvoid nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session)throws HibernateException, SQLException// 5{
java.util.Date d = (java.util.Date) value;
Hibernate.INTEGER.nullSafeSet(st, Dates.getYear(d), index);
Hibernate.INTEGER.nullSafeSet(st, Dates.getMonth(d), index + 1);
Hibernate.INTEGER.nullSafeSet(st, Dates.getDay(d), index + 2);}publicObject deepCopy(Object value)throws HibernateException {
java.util.Date d = (java.util.Date) value;if(value == null)returnnull;return(java.util.Date) d.clone();}publicboolean isMutable(){returntrue;}publicSerializable disassemble(Object value, SessionImplementor session)throws HibernateException
{return(Serializable) deepCopy(value);}publicObject assemble(Serializable cached, SessionImplementor session, Object owner)throws HibernateException
{return deepCopy(cached);}publicObject replace(Object original, Object target, SessionImplementor session, Object owner)throws HibernateException
{return deepCopy(original);}}
Le convertisseur implémente l'interface CompositeUserType (1). Les méthodes clés sont getPropertyValue() (2) et setPropertyValue() (3) pour récupérer respectivement définir les valeurs des propriétés de l'objet et nullSafeGet() (4) et nullSafeSet() (5) pour lire et enregistrer l'objet de et dans la base de données.
Conversion de références
La conversion de références n'est pas pris en charge directement par Hibernate. Dans certaines très rares circonstances, vous devrez peut-être faire appel à une conversion de référence. Cette section vous explique comment faire.
Par exemple, vous pourriez avoir une référence vers une entité permis de conduire (driving licence) qui utilise deux colonnes DRIVINGLICENCE_LEVEL et DRIVINGLICENCE_TYPE et cette dernière colonne n'accepte pas de valeurs nulles , mais il est quand même possible que l'objet père puisse ne pas avoir de références vers une entité permis de conduire. Dans ce cas, il faut que la colonne DRIVINGLICENCE_TYPE contienne une chaîne de caractères vide. Ceci n'est pas un cas normal si vous cconcevez une base de données en utilisant des clés étrangères, mais si elle a été conçue par un développeur RPG, c'était la manière de faire, car les développeurs RPG ne savaient pas travailler avec des valeurs nulles.
Cela signifie que vous avez besoin d'un convertisseur pour la colonne DRIVINGLICENCE_TYPE pour transformer null en chaîne de caractère vide. Ceci peut être résolu avec un convertisseur comme celui-ci :
// Nous appliquons la conversion (null vers chaîne de caractères vide) à la colonne// DRIVINGLICENCE_TYPE.// Afin d'y arriver, nous créons drivingLicence_level et drivingLicence_type,// nous rendons les annotations @JoinColumns ni insérables ni modifiables,// nous modifions les méthodes get/setDrivingLicence, puis nous créons la méthode// drivingLicenceConversion().
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumns({// 1
@JoinColumn(name="DRIVINGLICENCE_LEVEL", referencedColumnName="LEVEL",
insertable=false, updatable=false),
@JoinColumn(name="DRIVINGLICENCE_TYPE", referencedColumnName="TYPE",
insertable=false, updatable=false)})private DrivingLicence drivingLicence;privateInteger drivingLicence_level;// 2privateString drivingLicence_type;// 2public DrivingLicence getDrivingLicence(){// 3// De cette manière parce que la colonne du type de permis de conduire n'admet// pas les valeurs nullestry{if(drivingLicence != null) drivingLicence.toString();// to force loadreturn drivingLicence;}catch(EntityNotFoundException ex){returnnull;}}publicvoid setDrivingLicence(DrivingLicence licence){// 4// De cette manière parce que la colonne du type de permis de conduire n'admet// pas les valeurs nullesthis.drivingLicence = licence;this.drivingLicence_level = licence==null?null:licence.getLevel();this.drivingLicence_type = licence==null?null:licence.getType();}
@PrePersist @PreUpdate
privatevoid drivingLicenceConversion(){// 5if(this.drivingLicence_type == null)this.drivingLicence_type = "";}
Premièrement, vous devez utiliser les annotations @JoinColumns avec les attributs insertable=false et updateable=false pour toutes les annotations @JoinColumn (1) de manière à ce que la référence soit lue depuis la base de données, mais pas écrite. Deuxièmement, définissez des propriétés standards pour les clés étrangères de la référence (2). Troisièmement, écrivez un getter, getDrivingLicence() (3), pour retourner null lorsque la référence n'est pas trouvée. Et quatrièmement, définissez un setter, setDrivingLicence() (4) pour assigner la clé de la référence aux propriétés correspondant aux clés étrangères.
Cinquièmement, et finalement, vous devez écrire une méthode de rappel, drivingLicenceConversion() (5), pour effectuer le travail de conversion. Cette méthode sera exécuté automatiquement lors des créations et des mises à jour.
Cette exemple montre comment l'on peut englober des base de données existantes avec un peu de programmation et quelques ressources de base de JPA.
Table of Contents
Chapitre 6. Mapping objet-relationnel
Le mapping objet-relationnel vous permet de définir dans quelles tables et colonnes de votre base de données l'état de votre entité sera sauvegardée.Les outils objet-relationnel vous permettent de travailler avec des objets plutôt qu'avec des tables et des colonnes et génèrent automatiquement le code SQL pour lire et mettre à jour la base de données. De cette manière, vous n'avez pas besoin d'un accès direct à la base de données SQL. Bien sûr, vous devez définir précisément la manière dont vos classes correspondent aux tables et ce travail est effectué à l'aide des annotations JPA. Les entités OpenXava sont des entités JPA, c'est pourquoi la persistance dans OpenXava est prise en charge par le Java Persistance API (JPA). Ce chapitre montre les techniques de base de mise en correspondance ainsi que quelques cas spéciaux. Si vous souhaitez en savoir plus sur JPA , consultez la documentation de Hibernate Annotations (l'implémentation JPA utilisée par défaut par OpenXava) ou tout autre manuel JPA que vous souhaitez.
Mapping d'entité
L'annotation @Table spécifie la table principale pour l'entité annotée. Des tables additionnelles peuvent être ajoutées avec les annotations @SecondaryTable ou @SecondaryTables. Si aucune annotation @Table n'est présente, une valeur par défaut sera appliquée. Exemple :Mapping de propriété
L'annotation @Column est utilisée pour spécifier la colonne d'une propriété persistante ou champ. Si aucune annotation @Column est écrite, une valeur par défaut est appliquée. Une simple exemple :Voici un exemple en spécifiant l'annotation pour une méthode getter :
D'autres exemples :
Mapping de référence
L'annotation @JoinColumn est utilisée pour spécifier une colonne représentant une référence. Exemple :Si vous avez besoin de définir un mapping pour des clés étrangères composites, il faut employer l'annotation @JoinColumns. Cette annotation groupe plusieurs @JoinColumn pour la même référence. Lorsque l'annotation @JoinColumns est utilisée, les deux attributs name et referencedColumnName doivent être définis dans chaque annotation @JoinColumn. Exemple :
Mapping de collection
Lorsque vous utilisez l'annotation @OneToMany pour une collection, le mapping dépende de la référence utilisée dans l'autre partie de l'association, c'est-à-dire que, généralement, il n'y a rien besoin de faire. Mais si vous utilisez l'annotation @ManyToMany, il est utile de spécifier la table de jointure avec l'annotation @JoinTable comme ceci :Si l'annotation @JoinTable manque, des valeurs par défaut sont appliquées
Please, translate to French: When you use @ElementCollection (new in v5.0) for a collection you can use @CollectionTable and @AttributeOverrides, as following:
Si l'annotation @CollectionTable au @AttributeOverrides manque, des valeurs par défaut sont appliquées
Mapping de référence embarquée
Une référence embarquée contient des données qui sont stockées dans la même table du modèle relationnel que l'entité principale. Par exemple, si vous avez une entité Address (adresse) embarquée associée à une entité Customer (acheteur), les données de l'adresse seront enregistrées dans la même table que les données de l'acheteur. Comment réaliser la mise en correspondance avec JPA ? En utilisant l'annotation @AttributeOverrides de cette manière :Si vous n'utilisez par @AttributeOverrides, des valeurs par défaut s'appliquent.
Conversion de type
La conversion de type entre Java et la base de donnée relationnelle est le travail de l'implémentation JPA (OpenXava utilise Hibernate par défaut). Normalement, la conversion par défaut est correcte pour la plupart des cas, mais si vous travaillez avec une base de donnée existante, vous auriez besoin des quelques astuces de cette section.Etant donné que OpenXava utilise les facilités d'Hibernate pour les conversions de type, vous pouvez en apprendre plus sur ce sujet en lisant la documentation Hibernate.
Conversion de propriétés
Lorsque le type d'une propriété Java et le type de la colonne de la base de donnée relationnelle ne correspondent pas, vous devez écrire un type Hibernate (Hibernate Type) afin d'exécuter votre conversion personnalisée.Par exemple, si vous avez une propriété de type String[] et que vous souhaitez enregistrer sa valeur en l'agrégeant dans une seule colonne de table de type VARCHAR, vous devez déclarer la conversion de votre propriété ainsi :
La logique de conversion dans RegionsType est la suivante :
Le convertisseur de type doit implémenter l'interface org.hibernate.usertype.UserType(1). Les principales méthodes sont nullSafeGet() (2) pour lire la valeur de la base de donnée et la convertir en Java et nullSafeSet() (3) pour écrire la valeur Java dans la base de données.
OpenXava offre des type génériques de convertisseurs dans le paquet org.openxava.types prêts à l'emploi. L'un de ceux-là est le convertisseur EnumLetterType qui permet de mettre en correspondance des type enum. Par exemple, si vous avez une propriété telle que celle-ci :
Dans cette propriété Java, 'LOCAL' vaut 1, 'NATIONAL' vaut 2 et 'INTERNATIONAL' vaut 3 lorsque la propriété est enregistrée dans la base de données. Mais que se passe-t-il si dans la base de données, une lettre doit être enregistrée ? Dans ce cas, vous pouvez utiliser EnumLetterType de cette façon :
Puisque vous spécifiez 'LNI' comme valeurs de l'attribut letters, le convertisseur de type fait correspondre 'L' à 1, 'N' à 2 et 'I' à 3. Remarquez aussi comment des convertisseurs peuvent être configurés en utilisant leurs propriétés ce qui les rend plus réutilisables.
Conversion de colonnes multiples
Avec l'annotation @CompositeUserType, vous pouvez faire correspondre plusieurs colonnes de table à une seule propriété Java. Ceci peut être utile si vous avez des propriétés définies par des classes personnalisées qui ont elles-mêmes plusieurs propriétés à enregistrer. Cette annotation est aussi utilisée lorsque vous travaillez avec une base de données existante.Une exemple type est les convertisseur générique Date3Type qui permet de stocker dans 3 colonnes de la base de données une seule propriété Java avec le type java.util.Date.
DAYDELIVERY, MONTHDELIVERY et YEARDELIVERY sont trois colonnes de la base de données qui enregistrent la valeur de la date de livraison. Voici le code source de Date3Type :
Le convertisseur implémente l'interface CompositeUserType (1). Les méthodes clés sont getPropertyValue() (2) et setPropertyValue() (3) pour récupérer respectivement définir les valeurs des propriétés de l'objet et nullSafeGet() (4) et nullSafeSet() (5) pour lire et enregistrer l'objet de et dans la base de données.
Conversion de références
La conversion de références n'est pas pris en charge directement par Hibernate. Dans certaines très rares circonstances, vous devrez peut-être faire appel à une conversion de référence. Cette section vous explique comment faire.Par exemple, vous pourriez avoir une référence vers une entité permis de conduire (driving licence) qui utilise deux colonnes DRIVINGLICENCE_LEVEL et DRIVINGLICENCE_TYPE et cette dernière colonne n'accepte pas de valeurs nulles , mais il est quand même possible que l'objet père puisse ne pas avoir de références vers une entité permis de conduire. Dans ce cas, il faut que la colonne DRIVINGLICENCE_TYPE contienne une chaîne de caractères vide. Ceci n'est pas un cas normal si vous cconcevez une base de données en utilisant des clés étrangères, mais si elle a été conçue par un développeur RPG, c'était la manière de faire, car les développeurs RPG ne savaient pas travailler avec des valeurs nulles.
Cela signifie que vous avez besoin d'un convertisseur pour la colonne DRIVINGLICENCE_TYPE pour transformer null en chaîne de caractère vide. Ceci peut être résolu avec un convertisseur comme celui-ci :
Premièrement, vous devez utiliser les annotations @JoinColumns avec les attributs insertable=false et updateable=false pour toutes les annotations @JoinColumn (1) de manière à ce que la référence soit lue depuis la base de données, mais pas écrite. Deuxièmement, définissez des propriétés standards pour les clés étrangères de la référence (2). Troisièmement, écrivez un getter, getDrivingLicence() (3), pour retourner null lorsque la référence n'est pas trouvée. Et quatrièmement, définissez un setter, setDrivingLicence() (4) pour assigner la clé de la référence aux propriétés correspondant aux clés étrangères.
Cinquièmement, et finalement, vous devez écrire une méthode de rappel, drivingLicenceConversion() (5), pour effectuer le travail de conversion. Cette méthode sera exécuté automatiquement lors des créations et des mises à jour.
Cette exemple montre comment l'on peut englober des base de données existantes avec un peu de programmation et quelques ressources de base de JPA.